package com.lambkit.db;

import cn.hutool.core.util.StrUtil;
import com.lambkit.core.Lambkit;
import com.lambkit.core.cache.ICache;
import com.lambkit.db.dialect.IDialect;
import com.lambkit.db.sql.Columns;
import com.lambkit.db.sql.Example;

import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * @author yangyong(孤竹行)
 */
public abstract class AbstractDbOpt<T extends IRowData, P extends IPageData<T>> implements IDbOpt<T, P> {
    private IDialect dialect;

    public AbstractDbOpt(IDialect dialect)  {
        this.dialect = dialect;
    }

    @Override
    public IDialect getDialect() {
        return dialect;
    }

    @Override
    public void setDialect(IDialect dialect) {
        this.dialect = dialect;
    }

    @Override
    public T findFirst(Sql sqlPara) {
        return findFirst(sqlPara.getSql(), sqlPara.getPara());
    }

    @Override
    public T findFirst(Example example) {
        return findFirst(dialect.findByExample(example, 1));
    }

    @Override
    public List<T> find(Sql sqlPara) {
        return find(sqlPara.getSql(), sqlPara.getPara());
    }

    @Override
    public List<T> find(Example example, Integer count) {
        return find(dialect.findByExample(example, count));
    }

    @Override
    public List<T> find(Example example) {
        return find(dialect.findByExample(example, null));
    }

    @Override
    public List<T> findAll(String tableName) {
        return find(dialect.findByExample(Example.create(tableName), null));
    }

    //public abstract P paginate(Integer pageNumber, Integer pageSize, Sql sqlPara);

    @Override
    public P paginate(Integer pageNumber, Integer pageSize, Example example) {
        return paginate(pageNumber, pageSize, dialect.paginateByExample(example));
    }

    @Override
    public P paginate(Sql sqlPara, Integer offset, Integer limit) {
        int pageSize = limit;
        int pageNumber = offset / pageSize + 1;
        return paginate(pageNumber, pageSize, sqlPara);
    }

    @Override
    public P paginate(Example example, Integer offset, Integer limit) {
        int pageSize = limit;
        int pageNumber = offset / pageSize + 1;
        return paginate(pageNumber, pageSize, example);
    }

    //public abstract Long count(Sql sqlPara);

    @Override
    public Long count(Example example) {
        return count(dialect.findByExample(example, null));
    }

    public abstract boolean save(T record);

    public abstract boolean delete(T record);

    @Override
    public int delete(Sql sqlPara) {
        return delete(sqlPara.getSql(), sqlPara.getPara());
    }

    @Override
    public int delete(Example example) {
        return delete(dialect.deleteByExample(example));
    }

    public abstract boolean update(T record);

    @Override
    public int update(Sql sqlPara) {
        return update(sqlPara.getSql(), sqlPara.getPara());
    }

    public abstract int update(String sql, Object... paras);

    @Override
    public int update(String sql) {
        return update(sql, new Object[] {});
    }

    @Override
    public boolean saveOrUpdate(T rowData) {
        if (null == primaryKeyValue(rowData)) {
            return this.save(rowData);
        }
        return this.update(rowData);
    }

    public Object primaryKeyValue(T rowData) {
        String primaryKey = rowData.primaryKey();
        if(StrUtil.isNotBlank(primaryKey)) {
            String[] keys = primaryKey.split(",");
            int pkCount = keys.length;
            if(pkCount == 1) {
                return rowData.get(primaryKey);
            } else if(pkCount > 1) {
                String res = "";
                for(int i=0; i<pkCount; i++) {
                    res += "," + rowData.get(keys[0].trim());
                }
                res = res.substring(1);
                return res;
            }
        }
        return null;
    }

    //public abstract List<T> find(String sql, Object... paras);

    @Override
    public List<T> find(String sql) {
        return find(sql, new Object[] {});
    }

    //public abstract   T findFirst(String sql, Object... paras);

    @Override
    public List<T> find(T rowData) {
        if(rowData == null ||
                StrUtil.isBlank(rowData.tableName())) {
            return null;
        }
        Set<String> columnNames = rowData.columnNames();
        Columns columns = Columns.create();
        for(String columnName : columnNames) {
            Object value = rowData.get(columnName);
            if(value == null) {
                continue;
            }
            columns.eq(columnName, value);
        }
        Example example = Example.create(rowData.tableName(), columns);
        return find(example);
    }

    @Override
    public T findFirst(String sql) {
        return findFirst(sql, new Object[] {});
    }

    @Override
    public T findFirst(T rowData) {
        if(rowData == null ||
            StrUtil.isBlank(rowData.tableName())) {
            return null;
        }
        Set<String> columnNames = rowData.columnNames();
        Columns columns = Columns.create();
        for(String columnName : columnNames) {
            Object value = rowData.get(columnName);
            if(value == null) {
                continue;
            }
            columns.eq(columnName, value);
        }
        Example example = Example.create(rowData.tableName(), columns);
        return findFirst(example);
    }

    @Override
    public T findById(String table, String primaryKeys, Object... idValues) {
        if(idValues == null || idValues.length == 0) {
            return null;
        }
        Columns columns = Columns.create();
        if(StrUtil.isNotBlank(primaryKeys)) {
            String[] keys = primaryKeys.split(",");
            if(keys.length != idValues.length) {
                throw new IllegalArgumentException("id values error, need " + keys.length + " id value");
            }
            for(int i=0; i< keys.length; i++) {
                columns.eq(keys[i].trim(), idValues[i]);
            }
        } else {
            columns.eq("id", idValues[0]);
        }
        Example example = Example.create(table, columns);
        return findFirst(example);
    }

    public int deleteById(String table, String primaryKeys, Object... idValues) {
        if(idValues == null || idValues.length == 0) {
            return 0;
        }
        Columns columns = Columns.create();
        if(StrUtil.isNotBlank(primaryKeys)) {
            String[] keys = primaryKeys.split(",");
            if(keys.length != idValues.length) {
                throw new IllegalArgumentException("id values error, need " + keys.length + " id value");
            }
            for(int i=0; i< keys.length; i++) {
                columns.eq(keys[i].trim(), idValues[i]);
            }
        } else {
            columns.eq("id", idValues[0]);
        }
        Example example = Example.create(table, columns);
        return delete(example);
    }

    public abstract int delete(String sql, Object... paras);

    @Override
    public int delete(String sql) {
        return delete(sql, new Object[] {});
    }

    @Override
    public <V extends IDbOpt> boolean tx(ITxAction<V> atom) {
        return tx(null, atom);
    }

    //public abstract boolean tx(Integer transactionLevel, ITxAction<V> atom);

    @Override
    public <V extends IDbOpt> Future<Boolean> txInNewThread(ITxAction<V> atom) {
        return txInNewThread(null, atom);
    }

    @Override
    public <V extends IDbOpt> Future<Boolean> txInNewThread(Integer transactionLevel, ITxAction<V> atom) {
        FutureTask<Boolean> task = new FutureTask(() -> {
            return this.tx(transactionLevel, atom);
        });
        Thread thread = new Thread(task);
        thread.start();
        return task;
    }

    @Override
    public List<T> findByCache(String cacheName, Object key, Sql sqlPara) {
        ICache cache = Lambkit.getCache();
        List<T> rowDataList = null;
        if(cache != null) {
            rowDataList = cache.get(cacheName, key);
            if(rowDataList != null) {
                return rowDataList;
            }
        }
        rowDataList = find(sqlPara);
        if(cache != null && rowDataList != null) {
            cache.put(cacheName, key, rowDataList);
        }
        return rowDataList;
    }

    @Override
    public List<T> findByCache(String cacheName, Object key, Example example) {
        return findByCache(cacheName, key, dialect.findByExample(example, null));
    }

    @Override
    public T findFirstByCache(String cacheName, Object key, Sql sqlPara) {
        ICache cache = Lambkit.getCache();
        T rowData = null;
        if(cache != null) {
            rowData = cache.get(cacheName, key);
            if(rowData != null) {
                return rowData;
            }
        }
        rowData = findFirst(sqlPara);
        if(cache != null && rowData != null) {
            cache.put(cacheName, key, rowData);
        }
        return rowData;
    }

    @Override
    public T findFirstByCache(String cacheName, Object key, Example example) {
        return findFirstByCache(cacheName, key, dialect.findByExample(example, 1));
    }

    @Override
    public P paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, Sql sqlPara) {
        ICache cache = Lambkit.getCache();
        P rowDataPage = null;
        if(cache != null) {
            rowDataPage = cache.get(cacheName, key);
            if(rowDataPage != null) {
                return rowDataPage;
            }
        }
        rowDataPage = paginate(pageNumber, pageSize, sqlPara);
        if(cache != null && rowDataPage != null) {
            cache.put(cacheName, key, rowDataPage);
        }
        return rowDataPage;
    }

    @Override
    public P paginateByCache(String cacheName, Object key, int pageNumber, int pageSize, Example example) {
        return paginateByCache(cacheName, key, pageNumber, pageSize, dialect.paginateByExample(example));
    }

    //public abstract int[] batch(String sql, Object[][] paras, int batchSize);

    //public abstract int[] batch(List<String> sqlList, int batchSize);

    //public abstract int[] batchSave(List<T> recordList, int batchSize);

    //public abstract int[] batchUpdate(List<T> recordList, int batchSize);
}
